use std::collections::BTreeMap;

use ruma::{
    api::client::{
        error::ErrorKind,
        room::{self, create_room},
    },
    events::{
        room::{
            canonical_alias::RoomCanonicalAliasEventContent,
            create::RoomCreateEventContent,
            history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
            join_rules::{JoinRule, RoomJoinRulesEventContent},
            member::{MembershipState, RoomMemberEventContent},
            name::RoomNameEventContent,
            topic::RoomTopicEventContent,
        },
        TimelineEventType,
    },
    int, CanonicalJsonObject, OwnedRoomAliasId, OwnedRoomId, RoomAliasId, RoomId, RoomVersionId,
};
use serde_json::{json, value::to_raw_value};
use tracing::{error, info, warn};

use crate::{core::pdu::builder::PduBuilder, server::server, Error, Result, Ruma};

/// # `POST /_matrix/client/v3/createRoom`
///
/// Creates a new room.
///
/// - Room ID is randomly generated
/// - Create alias if `room_alias_name` is set
/// - Send create event
/// - Join sender user
/// - Send power levels event
/// - Send canonical room alias
/// - Send join rules
/// - Send history visibility
/// - Send guest access
/// - Send events listed in initial state
/// - Send events implied by `name` and `topic`
/// - Send invite events
pub(crate) async fn create_room_route(
    body: Ruma<create_room::v3::Request>,
) -> Result<create_room::v3::Response> {
    use create_room::v3::RoomPreset;

    let sender_user = body.sender_user.as_ref().expect("user is authenticated");

    let room_id: OwnedRoomId = RoomId::new(&server().config.server_name);

    // check if room ID doesn't already exist instead of erroring on auth check
    // if services.rooms.short.get_shortroomid(&room_id)?.is_some() {
    //     return Err(Error::BadRequest(
    //         ErrorKind::RoomInUse,
    //         "Room with that custom room ID already exists",
    //     ));
    // }

    // let _short_id = services.rooms.short.get_or_create_shortroomid(&room_id)?;
    let state_lock = server().rooms.state.mutex.clone().lock_owned().await;

    let alias: Option<OwnedRoomAliasId> = if let Some(alias) = &body.room_alias_name {
        Some(RoomAliasId::parse(&alias).expect("alias is valid"))
        // Some(room_alias_check(&services, alias, &body.appservice_info).await?)
    } else {
        None
    };

    let room_version = match body.room_version.clone() {
        Some(room_version) => {
            // if services
            //     .globals
            //     .supported_room_versions()
            //     .contains(&room_version)
            // {
            room_version
            // } else {
            //     return Err(Error::BadRequest(
            //         ErrorKind::UnsupportedRoomVersion,
            //         "This server does not support that room version.",
            //     ));
            // }
        }
        None => server().config.default_room_version.clone(),
    };
    let content = match &body.creation_content {
        Some(content) => {
            use RoomVersionId::*;

            let mut content = content
                .deserialize_as::<CanonicalJsonObject>()
                .map_err(|e| {
                    error!("Failed to deserialise content as canonical JSON: {}", e);
                    Error::bad_database("Failed to deserialise content as canonical JSON.")
                })?;
            match room_version {
                V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
                    content.insert(
                        "creator".into(),
                        json!(&sender_user).try_into().map_err(|e| {
                            info!("Invalid creation content: {e}");
                            Error::BadRequest(ErrorKind::BadJson, "Invalid creation content")
                        })?,
                    );
                }
                _ => {
                    // V11+ removed the "creator" key
                }
            }
            content.insert(
                "room_version".into(),
                json!(room_version.as_str()).try_into().map_err(|_| {
                    Error::BadRequest(ErrorKind::BadJson, "Invalid creation content")
                })?,
            );
            content
        }
        None => {
            use RoomVersionId::*;

            let content = match room_version {
                V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
                    RoomCreateEventContent::new_v1(sender_user.clone())
                }
                _ => RoomCreateEventContent::new_v11(),
            };
            let mut content = serde_json::from_str::<CanonicalJsonObject>(
                to_raw_value(&content)
                    .expect("we just created this as content was None")
                    .get(),
            )
            .unwrap();
            content.insert(
                "room_version".into(),
                json!(room_version.as_str())
                    .try_into()
                    .expect("we just created this as content was None"),
            );
            content
        }
    };

    // 1. The room create event
    server()
        .rooms
        .timeline
        .build_and_append_pdu(
            PduBuilder {
                event_type: TimelineEventType::RoomCreate,
                content: to_raw_value(&content).expect("event is valid, we just created it"),
                unsigned: None,
                state_key: Some(String::new()),
                redacts: None,
                timestamp: None,
            },
            sender_user,
            &room_id,
            &state_lock,
        )
        .await?;

    // 2. Let the room creator join
    server()
        .rooms
        .timeline
        .build_and_append_pdu(
            PduBuilder {
                event_type: TimelineEventType::RoomMember,
                content: to_raw_value(&RoomMemberEventContent {
                    membership: MembershipState::Join,
                    displayname: None,
                    avatar_url: None,
                    is_direct: Some(body.is_direct),
                    third_party_invite: None,
                    reason: None,
                    join_authorized_via_users_server: None,
                })
                .expect("event is valid, we just created it"),
                unsigned: None,
                state_key: Some(sender_user.to_string()),
                redacts: None,
                timestamp: None,
            },
            sender_user,
            &room_id,
            &state_lock,
        )
        .await?;

    // 3. Power levels

    // Figure out preset. We need it for preset specific events
    let preset = body.preset.clone().unwrap_or(match &body.visibility {
        room::Visibility::Public => RoomPreset::PublicChat,
        _ => RoomPreset::PrivateChat, // Room visibility should not be custom
    });

    let mut users = BTreeMap::from_iter([(sender_user.clone(), int!(100))]);

    if preset == RoomPreset::TrustedPrivateChat {
        for invite_ in &body.invite {
            users.insert(invite_.clone(), int!(100));
        }
    }

    // let power_levels_content =
    //     default_power_levels_content(&body.power_level_content_override, &body.visibility, users)?;

    // server()
    //     .rooms
    //     .timeline
    //     .build_and_append_pdu(
    //         PduBuilder {
    //             event_type: TimelineEventType::RoomPowerLevels,
    //             content: to_raw_value(&power_levels_content)
    //                 .expect("to_raw_value always works on serde_json::Value"),
    //             unsigned: None,
    //             state_key: Some(String::new()),
    //             redacts: None,
    //             timestamp: None,
    //         },
    //         sender_user,
    //         &room_id,
    //         &state_lock,
    //     )
    //     .await?;

    // 4. Canonical room alias
    if let Some(room_alias_id) = &alias {
        server()
            .rooms
            .timeline
            .build_and_append_pdu(
                PduBuilder {
                    event_type: TimelineEventType::RoomCanonicalAlias,
                    content: to_raw_value(&RoomCanonicalAliasEventContent {
                        alias: Some(room_alias_id.to_owned()),
                        alt_aliases: vec![],
                    })
                    .expect("We checked that alias earlier, it must be fine"),
                    unsigned: None,
                    state_key: Some(String::new()),
                    redacts: None,
                    timestamp: None,
                },
                sender_user,
                &room_id,
                &state_lock,
            )
            .await?;
    }

    // 5. Events set by preset

    // 5.1 Join Rules
    server()
        .rooms
        .timeline
        .build_and_append_pdu(
            PduBuilder {
                event_type: TimelineEventType::RoomJoinRules,
                content: to_raw_value(&RoomJoinRulesEventContent::new(match preset {
                    RoomPreset::PublicChat => JoinRule::Public,
                    // according to spec "invite" is the default
                    _ => JoinRule::Invite,
                }))
                .expect("event is valid, we just created it"),
                unsigned: None,
                state_key: Some(String::new()),
                redacts: None,
                timestamp: None,
            },
            sender_user,
            &room_id,
            &state_lock,
        )
        .await?;

    // 5.2 History Visibility
    server()
        .rooms
        .timeline
        .build_and_append_pdu(
            PduBuilder {
                event_type: TimelineEventType::RoomHistoryVisibility,
                content: to_raw_value(&RoomHistoryVisibilityEventContent::new(
                    HistoryVisibility::Shared,
                ))
                .expect("event is valid, we just created it"),
                unsigned: None,
                state_key: Some(String::new()),
                redacts: None,
                timestamp: None,
            },
            sender_user,
            &room_id,
            &state_lock,
        )
        .await?;

    // 5.3 Guest Access
    // server()
    //     .rooms
    //     .timeline
    //     .build_and_append_pdu(
    //         PduBuilder {
    //             event_type: TimelineEventType::RoomGuestAccess,
    //             content: to_raw_value(&RoomGuestAccessEventContent::new(match preset {
    //                 RoomPreset::PublicChat => GuestAccess::Forbidden,
    //                 _ => GuestAccess::CanJoin,
    //             }))
    //             .expect("event is valid, we just created it"),
    //             unsigned: None,
    //             state_key: Some(String::new()),
    //             redacts: None,
    //             timestamp: None,
    //         },
    //         sender_user,
    //         &room_id,
    //         &state_lock,
    //     )
    //     .await?;

    // 6. Events listed in initial_state
    for event in &body.initial_state {
        let mut pdu_builder = event.deserialize_as::<PduBuilder>().map_err(|e| {
            warn!("Invalid initial state event: {:?}", e);
            Error::BadRequest(ErrorKind::InvalidParam, "Invalid initial state event.")
        })?;

        info!("Room creation initial state event: {event:?}");

        // client/appservice workaround: if a user sends an initial_state event with a
        // state event in there with the content of literally `{}` (not null or empty
        // string), let's just skip it over and warn.
        if pdu_builder.content.get().eq("{}") {
            info!("skipping empty initial state event with content of `{{}}`: {event:?}");
            warn!("content: {}", pdu_builder.content.get());
            continue;
        }

        // Implicit state key defaults to ""
        pdu_builder.state_key.get_or_insert_with(String::new);

        // Silently skip encryption events if they are not allowed
        if pdu_builder.event_type == TimelineEventType::RoomEncryption
            && !server().global.allow_encryption()
        {
            continue;
        }

        server()
            .rooms
            .timeline
            .build_and_append_pdu(pdu_builder, sender_user, &room_id, &state_lock)
            .await?;
    }

    // 7. Events implied by name and topic
    if let Some(name) = &body.name {
        server()
            .rooms
            .timeline
            .build_and_append_pdu(
                PduBuilder {
                    event_type: TimelineEventType::RoomName,
                    content: to_raw_value(&RoomNameEventContent::new(name.clone()))
                        .expect("event is valid, we just created it"),
                    unsigned: None,
                    state_key: Some(String::new()),
                    redacts: None,
                    timestamp: None,
                },
                sender_user,
                &room_id,
                &state_lock,
            )
            .await?;
    }

    if let Some(topic) = &body.topic {
        server()
            .rooms
            .timeline
            .build_and_append_pdu(
                PduBuilder {
                    event_type: TimelineEventType::RoomTopic,
                    content: to_raw_value(&RoomTopicEventContent {
                        topic: topic.clone(),
                    })
                    .expect("event is valid, we just created it"),
                    unsigned: None,
                    state_key: Some(String::new()),
                    redacts: None,
                    timestamp: None,
                },
                sender_user,
                &room_id,
                &state_lock,
            )
            .await?;
    }

    // 8. Events implied by invite (and TODO: invite_3pid)
    drop(state_lock);
    // for user_id in &body.invite {
    //     if let Err(e) = invite_helper(
    //         &services,
    //         sender_user,
    //         user_id,
    //         &room_id,
    //         None,
    //         body.is_direct,
    //     )
    //     .await
    //     {
    //         warn!(%e, "Failed to send invite");
    //     }
    // }

    // Homeserver specific stuff
    // if let Some(alias) = alias {
    //     server()
    //         .rooms
    //         .alias
    //         .set_alias(&alias, &room_id, sender_user)?;
    // }

    // if body.visibility == room::Visibility::Public {
    //     services.rooms.directory.set_public(&room_id)?;
    // }

    info!("{sender_user} created a room with room ID {room_id}");

    Ok(create_room::v3::Response::new(room_id))
}
